#!/usr/bin/env python
# coding: utf-8

# # 文本数据加载与增强

# 随着可获得的文本数据逐步增多，对文本数据进行预处理，以便获得可用于网络训练所需干净数据的诉求也更为迫切。文本数据集预处理通常包括文本数据集加载与数据增强两部分。
# 
# 文本数据加载通常包含以下三种方式：
# 
# ## 加载文本数据
# 
# 下面我们以从TXT文件中读取数据为例，介绍`TextFileDataset`的使用方式，更多文本数据集加载相关信息可参考[API文档](http://58.48.42.237/luojiaNet/luojiaNetapi/#文本)。
# 
# 1中低阶API实现深度学习. 准备文本数据，内容如下：
# 
# ```text
# Welcome to Beijing
# 北京欢迎您！
# 我喜欢China!
# ```
# 
# 2高级数据集管理. 创建`tokenizer.txt`文件并复制文本数据到该文件中，将该文件存放在./datasets路径下。执行如下代码：

# In[1中低阶API实现深度学习]:

#数据集结构为：
# 
# ```text
# ./data
# └── tokenizer.txt
# ```
# 
# 从TXT文件中加载数据集并打印。代码如下：

# In[2高级数据集管理]:


import luojianet.dataset as ds
import luojianet.dataset.text as text

# 定义文本数据集的加载路径
DATA_FILE = './data/tokenizer.txt'

# 从tokenizer.txt中加载数据集
dataset = ds.TextFileDataset(DATA_FILE, shuffle=False)

for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# ## 文本数据增强
# 
# 针对文本数据增强，常用操作包含文本分词、词汇表查找等：
# 
# - 文本分词：将原始一长串句子分割成多个基本的词汇。
# - 词汇表查找：查找分割后各词汇对应的id，并将句子中包含的id组成词向量传入网络进行训练。
# 
# 下面对数据增强过程中用到的分词功能、词汇表查找等功能进行介绍，更多关于文本处理API的使用说明，可以参考[API文档](http://58.48.42.237/luojiaNet/luojiaNetapi/)。
# 
# ### 构造与使用词汇表
# 
# 词汇表提供了单词与id对应的映射关系，通过词汇表，输入单词能找到对应的单词id，反之依据单词id也能获取对应的单词。
# 
# luojianet提供了多种构造词汇表（Vocab）的方法，可以从字典、文件、列表以及Dataset对象中获取原始数据，以便构造词汇表，对应的接口为：[from_dict]
# 
# 以from_dict为例，构造Vocab的方式如下，传入的dict中包含多组单词和id对。

# In[3图像处理]:


from luojianet.dataset import text

# 构造词汇表
vocab = text.Vocab.from_dict({"home": 3, "behind": 2, "the": 4, "world": 5, "<unk>": 6})


# Vocab提供了单词与id之间相互查询的方法，即：[tokens_to_ids]方法，用法如下所示：

# In[4自然语言]:


# 根据单词查找id
ids = vocab.tokens_to_ids(["home", "world"])
print("ids: ", ids)

# 根据id查找单词
tokens = vocab.ids_to_tokens([2, 5])
print("tokens: ", tokens)


# 从上面打印的结果可以看出：
# 
# - 单词`"home"`和`"world"`的id分别为`3图像处理`和`5`；
# - id为`2高级数据集管理`的单词为`"behind"`，id为`5`的单词为`"world"`；
# 
# 这一结果也与词汇表一致。此外Vocab也是多种分词器（如WordpieceTokenizer）的必要入参，分词时会将句子中存在于词汇表的单词，前后分割开，变成单独的一个词汇，之后通过查找词汇表能够获取对应的词汇id。
# 
# ### 分词器
# 
# 分词就是将连续的字序列按照一定的规范划分成词序列的过程，合理的分词有助于语义理解。
# 
# luojianet提供了多种不同用途的分词器，如BasicTokenizer、BertTokenizer、JiebaTokenizer等，能够帮助用户高性能地处理文本。用户可以构建自己的字典，使用适当的标记器将句子拆分为不同的标记，并通过查找操作获取字典中标记的索引。此外，用户也可以根据需要实现自定义的分词器。
# 
# > 下面介绍几种常用分词器的使用方法，更多分词器相关信息请参考[API文档](http://58.48.42.237/luojiaNet/luojiaNetapi/)。
# 
# #### BertTokenizer
# 
# `BertTokenizer`操作是通过调用`BasicTokenizer`和`WordpieceTokenizer`来进行分词的。
# 
# 下面的样例首先构建了一个文本数据集和字符串列表，然后通过`BertTokenizer`对数据集进行分词，并展示了分词前后的文本结果。

# In[5]:


import luojianet.dataset as ds
import luojianet.dataset.text as text

# 构造待分词数据
input_list = ["床前明月光", "疑是地上霜", "举头望明月", "低头思故乡", "I am making small mistakes during working hours",
              "😀嘿嘿😃哈哈😄大笑😁嘻嘻", "繁體字"]

# 加载文本数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# 上面为数据集未被分词前的数据打印情况，下面使用`BertTokenizer`分词器对数据集进行分词。

# In[6]:


###################################BertTokenizer分词器,windows暂不支持##############################
# 构建词汇表
vocab_list = [
    "床", "前", "明", "月", "光", "疑", "是", "地", "上", "霜", "举", "头", "望", "低", "思", "故", "乡",
    "繁", "體", "字", "嘿", "哈", "大", "笑", "嘻", "i", "am", "mak", "make", "small", "mistake",
    "##s", "during", "work", "##ing", "hour", "😀", "😃", "😄", "😁", "+", "/", "-", "=", "12",
    "28", "40", "16", " ", "I", "[CLS]", "[SEP]", "[UNK]", "[PAD]", "[MASK]", "[unused1]", "[unused10]"]

# 加载词汇表
vocab = text.Vocab.from_list(vocab_list)

# 使用BertTokenizer分词器对文本数据集进行分词操作,windows暂不支持
tokenizer_op = text.BertTokenizer(vocab=vocab)
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']))


# 从上面两次的打印结果可以看出，数据集中的句子、词语和表情符号等都被`BertTokenizer`分词器以词汇表中的词汇为最小单元进行了分割，“故乡”被分割成了‘故’和‘乡’，“明月”被分割成了‘明’和‘月’。值得注意的是，“mistakes”被分割成了‘mistake’和‘##s’。
# 
# #### JiebaTokenizer
# 
# `JiebaTokenizer`操作是基于jieba的中文分词。
# 
# 以下示例代码完成下载字典文件`hmm_model.utf8`和`jieba.dict.utf8`，并将其放到指定位置。

# In[7]:

'''
手动下载
# 字典文件存放路径："./dictionary"
# 获取字典文件源
#"https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/datasets/hmm_model.utf8"
#"https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/datasets/jieba.dict.utf8"

'''
# 下载的文件放置的目录结构如下：
# 
# ```text
# ./dictionary/
# ├── hmm_model.utf8
# └── jieba.dict.utf8
# ```
# 
# 下面的样例首先构建了一个文本数据集，然后使用HMM与MP字典文件创建`JiebaTokenizer`对象，并对数据集进行分词，最后展示了分词前后的文本结果。

# In[8]:


import luojianet.dataset as ds
import luojianet.dataset.text as text

# 构造待分词数据
input_list = ["明天天气太好了我们一起去外面玩吧"]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# 上面为数据集未被分词前的数据打印情况，下面使用`JiebaTokenizer`分词器对数据集进行分词。

# In[9]:


HMM_FILE = "./dictionary/hmm_model.utf8"
MP_FILE = "./dictionary/jieba.dict.utf8"

# 使用JiebaTokenizer分词器对数据集进行分词
jieba_op = text.JiebaTokenizer(HMM_FILE, MP_FILE)
dataset = dataset.map(operations=jieba_op, input_columns=["text"], num_parallel_workers=1)

print("------------------------after tokenization-----------------------------")
for data in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(data['text']))


# 从上面两次打印结果来看，数据集中的句子被`JiebaTokenizer`分词器以词语为最小单元进行了划分。
# 
# #### SentencePieceTokenizer
# 
# `SentencePieceTokenizer`操作是基于开源自然语言处理工具包[SentencePiece](https://github.com/google/sentencepiece)封装的分词器。
# 
# 以下示例代码将下载文本数据集文件`botchan.txt`，并将其放置到指定位置。

# In[10]:

'''
手动下载数据
# 数据集存放位置： "./datasets"
# 语料数据源 "https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/datasets/botchan.txt"

'''

# 下载的文件放置的目录结构如下：
# 
# ```text
# ./datasets/
# └── botchan.txt
# ```
# 
# 下面的样例首先构建了一个文本数据集，然后从`vocab_file`文件中构建一个`vocab`对象，再通过`SentencePieceTokenizer`对数据集进行分词，并展示了分词前后的文本结果。

# In[11]:


import luojianet.dataset as ds
import luojianet.dataset.text as text
from luojianet.dataset.text import SentencePieceModel, SPieceTokenizerOutType

# 构造待分词数据
input_list = ["Nothing in the world is difficult for one who sets his mind on it."]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# 上面为数据集未被分词前的数据打印情况，下面使用`SentencePieceTokenizer`分词器对数据集进行分词。

# In[12]:


# 语料数据文件存放路径
vocab_file = "./datasets/botchan.txt"

# 从语料数据中学习构建词汇表
vocab = text.SentencePieceVocab.from_file([vocab_file], 5000, 0.9995, SentencePieceModel.WORD, {})

# 使用SentencePieceTokenizer分词器对数据集进行分词
tokenizer_op = text.SentencePieceTokenizer(vocab, out_type=SPieceTokenizerOutType.STRING)
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']))


# 从上面两次打印结果来看，数据集中的句子被`SentencePieceTokenizer`分词器以词语为最小单元进行了划分。在`SentencePieceTokenizer`分词器的处理过程中，空格作为普通符号处理，并使用下划线标记空格。
# 
# #### UnicodeCharTokenizer
# 
# `UnicodeCharTokenizer`操作是根据Unicode字符集来分词的。
# 
# 下面的样例首先构建了一个文本数据集，然后通过`UnicodeCharTokenizer`对数据集进行分词，并展示了分词前后的文本结果。

# In[13]:


import luojianet.dataset as ds
import luojianet.dataset.text as text

# 构造待分词数据
input_list = ["Welcome to Beijing!", "北京欢迎您！", "我喜欢China!"]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# 上面为数据集未被分词前的数据打印情况，下面使用`UnicodeCharTokenizer`分词器对数据集进行分词。

# In[14]:


# 使用UnicodeCharTokenizer分词器对数据集进行分词
tokenizer_op = text.UnicodeCharTokenizer()
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for data in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(data['text']).tolist())


# 从上面两次打印结果可以看出，数据集中的句子被`UnicodeCharTokenizer`分词器进行分割，中文以单个汉字为最小单元，英文以单个字母为最小单元。
# 
# #### WhitespaceTokenizer
# 
# `WhitespaceTokenizer`操作是根据空格来进行分词的。
# 
# 下面的样例首先构建了一个文本数据集，然后通过`WhitespaceTokenizer`对数据集进行分词，并展示了分词前后的文本结果。

# In[15]:


import luojianet.dataset as ds
import luojianet.dataset.text as text

# 构造待分词数据
input_list = ["Welcome to Beijing!", "北京欢迎您！", "我喜欢China!", "床前明月光，疑是地上霜。"]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# 上面为数据集未被分词前的数据打印情况，下面使用`WhitespaceTokenizer`分词器对数据集进行分词。

# In[16]:


################################Windows平台尚不支持 WhitespaceTokenizer#####################
# 使用WhitespaceTokenizer分词器对数据集进行分词
tokenizer_op = text.WhitespaceTokenizer()
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']).tolist())


# 从上面两次打印结果可以看出，数据集中的句子被`WhitespaceTokenizer`分词器以空格为分隔符进行分割。
# 
# #### WordpieceTokenizer
# 
# `WordpieceTokenizer`操作是基于单词集来进行划分的，划分依据可以是单词集中的单个单词，或者多个单词的组合形式。
# 
# 下面的样例首先构建了一个文本数据集，然后从单词列表中构建`vocab`对象，通过`WordpieceTokenizer`对数据集进行分词，并展示了分词前后的文本结果。

# In[17]:


import luojianet.dataset as ds
import luojianet.dataset.text as text

# 构造待分词数据
input_list = ["My", "favorite", "book", "is", "love", "during", "the", "cholera", "era", ".", "what",
              "我", "最", "喜", "欢", "的", "书", "是", "霍", "乱", "时", "期", "的", "爱", "情", "。", "好"]

# 构造英文词汇表
vocab_english = ["book", "cholera", "era", "favor", "##ite", "My", "is", "love", "dur", "##ing", "the", "."]

# 构造中文词汇表
vocab_chinese = ['我', '最', '喜', '欢', '的', '书', '是', '霍', '乱', '时', '期', '爱', '情', '。']

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))


# 上面为数据集未被分词前的数据打印情况，此处特意构造了词汇表中没有的单词“what”和“好”，下面使用`WordpieceTokenizer`分词器对数据集进行分词。

# In[18]:


# 使用WordpieceTokenizer分词器对数据集进行分词
vocab = text.Vocab.from_list(vocab_english+vocab_chinese)
tokenizer_op = text.WordpieceTokenizer(vocab=vocab)
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']))


# 从上面两次打印结果可以看出，数据集中的词语被`WordpieceTokenizer`分词器以构造的词汇表进行分词，“My”仍然被分为“My”，“love”仍然被分为“love”。值得注意的是，“favorite”被分为了“favor”和“##ite”，由于“word”和“好”在词汇表中未找到，所以使用\[UNK\]表示。
